package com.uxsino.reactorq.clientserver;

import java.io.IOException;
import java.net.URL;

import javax.jms.JMSException;
import javax.jms.QueueConnection;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.BlobMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.uxsino.reactorq.commons.Response;

/**
 * 
 * to call a service via JMS queue and await. parameters/result are transfered in message queue and the call() methods will wait for
 * the result until is is timed out.
 * parameter type and result type should be able to be encode/decode in json format, using fastxml's {@link ObjectMapper}. 
 * @param <ArgType> the parameter type
 * @param <ResponseType> the result type
 */
public class JMSSyncClient<ArgType, ResponseType> {

    private static Logger logger = LoggerFactory.getLogger(JMSSyncClient.class);

    private QueueConnection connection;

    private Session session;

    private javax.jms.Queue queue;

    private String queueName;

    private long millisTimeout = 10000; // use 10s for timeout by default

    // use customized version of "Requester" for timeout feature
    private com.uxsino.reactorq.QueueRequester requestor;

    private Class<ResponseType> responseType;

    private ObjectMapper mapper = new ObjectMapper();

    /**
     * 
     * exception throwned by JMSSyncClient
     *
     */
    public static final class ClientException extends Exception {
        /**
         * 
         */
        private static final long serialVersionUID = -936205001476287789L;

        public ClientException(String message) {
            super(message);
        }
    }

    public JMSSyncClient(QueueConnection connection, String queueName, Class<ResponseType> clsResponse)
        throws JMSException {
        responseType = clsResponse;
        this.connection = connection;
        this.queueName = queueName;

        if (connection != null) {
            connect();
        }
    }

    private void connect() throws JMSException {
        // session = this.connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
        session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        queue = session.createQueue(queueName);
        requestor = new com.uxsino.reactorq.QueueRequester(session, queue);
        connection.start();

    }

    /**
     * call the service and await until result returned or time out.
     * @param arg
     * @param receiverId an id to specify which server to call, if it is null or empty, a broadcast message will be sent
     * therefore, any server could response to this call.
     * @return
     * @throws ClientException
     */
    public ResponseType call(ArgType arg, String receiverId) throws ClientException {
        return call(arg, receiverId, this.millisTimeout);
    }

    /**
     * call the service and await until result returned or time out.
     * @param arg
     * @param receiverId an id to specify which server to call, if it is null or empty, a broadcast message will be sent
     * therefore, any server could response to this call.
     * @param millisTimeout timeout milliseconds
     * @return
     * @throws ClientException
     */
    public ResponseType call(ArgType arg, String receiverId, long millisTimeout) throws ClientException {
        return call(arg, receiverId, null, millisTimeout);
    }

    /**
     * call the service and await until result returned or time out.
     * @param arg
     * @param receiverId an id to specify which server to call, if it is null or empty, a broadcast message will be sent
     * therefore, any server could response to this call.
     * @param attachment url for the attachement file see {@link BlobMessage}
     * @return
     * @throws ClientException
     */
    public ResponseType call(ArgType arg, String receiverId, URL attachment) throws ClientException {
        return call(arg, receiverId, attachment, this.millisTimeout);
    }

    /**
     * call the service and await until result returned or time out.
     * @param arg
     * @param receiverId an id to specify which server to call, if it is null or empty, a broadcast message will be sent
     * therefore, any server could response to this call.
     * @param millisTimeout timeout milliseconds
     * @param attachment url for the attachement file see {@link BlobMessage}
     * @return
     * @throws ClientException
     */
    public ResponseType call(ArgType arg, String receiverId, URL attachment, long millisTimeout) throws ClientException {

        if (connection == null) {
            // connecting failed
            throw new ClientException("clinet not connected");
        }
        try {
            javax.jms.Message msg;

            String msgText = mapper.writeValueAsString(arg);
            if (attachment == null) {
                TextMessage textMsg = session.createTextMessage();
                textMsg.setText(msgText);
                msg = textMsg;
            } else {
                org.apache.activemq.BlobMessage blobMsg = ((org.apache.activemq.ActiveMQSession) session)
                    .createBlobMessage(attachment);
                blobMsg.setStringProperty("text", msgText);
                msg = blobMsg;
            }

            msg.setStringProperty("receiverId", receiverId);

            javax.jms.Message response = requestor.request(msg, millisTimeout);

            if (response == null)
                throw new ClientException("null response while calling");

            Response r = mapper.readValue(((TextMessage) response).getText(), Response.class);

            if (r.status != Response.STATUS_SUCCESS) {
                ClientException ce = new ClientException("server side error. " + r.j);
                throw ce;
            }

            return mapper.readValue(r.j, responseType);
        } catch (JMSException e) {
            ClientException ce = new ClientException("error calling event");
            ce.initCause(e);
            throw ce;
        } catch (JsonProcessingException e) {
            ClientException ce = new ClientException("error encoding event to json");
            ce.initCause(e);
            throw ce;

        } catch (IOException e) {
            ClientException ce = new ClientException("error decoding response json");
            ce.initCause(e);
            throw ce;
        }

    }

    /**
     * get queue name internally generated for the call
     * @return
     */
    public String getQueueName() {
        return queueName;
    }

    /**
     * set the timeout in milliseconds
     * @param millisTimeout
     * @return
     */
    public JMSSyncClient<ArgType, ResponseType> setTimeout(long millisTimeout) {
        this.millisTimeout = millisTimeout;
        return this;
    }

}
